#include "Camera.h"
#include "MatUtil.h"
#include "Mat3.h"
#include "IRenderer.h"
#include "Color.h"

Vec3 Camera::INITIAL_EYE = Vec3(2, 2, 2);
Vec3 Camera::INITIAL_AT = Vec3(0, 0, 0);
Vec3 Camera::INITIAL_UP = Vec3(0, 1, 0);

Camera::Camera(const Vec3& eye, const Vec3& at, const Vec3& up)
{
	CvarSystem& cvars = CvarSystem::getInstance();

	_type = cvars.addCvar("c_type", ORTHOGRAPHIC);
	_transformType = cvars.addCvar("c_transform_type", VIEW_FRAME);
	_left = cvars.addCvar("c_left", -1.0f);
	_right = cvars.addCvar("c_right", 1.0f);
	_bottom = cvars.addCvar("c_bottom", -1.0f);
	_top = cvars.addCvar("c_top", 1.0f);		
	_near = cvars.addCvar("c_near", 1.0f);
	_far = cvars.addCvar("c_far", 10.0f);
	_fovy = cvars.addCvar("c_fov_y", 90.0f);	
	_zoom = cvars.addCvar("c_zoom", 1.0f);

	_width = CvarSystem::getCvar("r_width");
	_height = CvarSystem::getCvar("r_height");

	_mark = cvars.addCvar("c_mark", false);
	_markSize = cvars.addCvar("c_mark_size", 0.1f);
	_markColor = cvars.addCvar("c_mark_color", YELLOW);	

	lookAt(eye, at, up);
}

void Camera::lookAt(const Vec3& eye, const Vec3& at, const Vec3& up)
{
	Vec3 n = (at-eye).normalize(); // z'
	Vec3 u = up.cross(n).normalize(); // x'
	Vec3 v = n.cross(u).normalize(); // y'

	Vec4 nh(n, 0.0f);
	Vec4 uh(u, 0.0f);
	Vec4 vh(v, 0.0f);

	Mat4 R = Mat4(uh, vh, nh, Vec4(0.0f, 0.0f, 0.0f, 1.0f)); // rotation
	Mat4 T = MatUtil::translate(-eye.x, -eye.y, -eye.z); // translation

	_translations = T;
	_rotations = R;
	_transform = R * T;
}

void Camera::move(float x, float y, float z)
{
	Mat4 T = MatUtil::translate(-x, -y, -z);

	if (_transformType->getValueI() == VIEW_FRAME) {
		_translations = T * _translations;
		_transform = T * _transform;		
	}
	else {
		_translations *= T;
		_transform *= T;		
	}	
}

void Camera::rotate(float x, float y, float z)
{
	//Mat4 T = MatUtil::inverseTranlations(_translations);
	//Mat4 TI = _translations;
	Mat4 R = MatUtil::rotate(x, y, z);

	_rotations = R * _rotations;
	_transform = _rotations * _translations;
}

Mat4 Camera::getTransform()
{
	return _transform;
}

Mat4 Camera::getProjection()
{
	float left = _left->getValueF() / _zoom->getValueF();
	float right = _right->getValueF() / _zoom->getValueF();
	float bottom = _bottom->getValueF() / _zoom->getValueF();
	float top = _top->getValueF() / _zoom->getValueF();
	float near = _near->getValueF();
	float far = _far->getValueF();
	float fovy = _fovy->getValueF();
	float aspect = (float) _width->getValueI() / _height->getValueI();

	switch (_type->getValueI()) {
	case PERSPECTTIVE_NORMAL:
		return frustum(left, right, bottom, top, near, far);
	case PERSPECTTIVE_FOV:
		return perspective(fovy, aspect, near, far);
	case ORTHOGRAPHIC:
	default:
		return ortho(left, right, bottom, top, near, far);
	}
}

Vec3 Camera::getPosition()
{
	Vec4 posh = _transform * Vec4(0.0f, 0.0f, 0.0f, 1.0f);
	return Vec3(-posh.x, -posh.y, -posh.z);
}

void Camera::getBounds(float *left, float *right, float *bottom, float *top, float *near, float *far)
{
	if (left != NULL) *left = _left->getValueF();
	if (right != NULL) *right = _right->getValueF();
	if (bottom != NULL) *bottom = _bottom->getValueF();
	if (top != NULL) *top = _top->getValueF();
	if (near != NULL) *near = _near->getValueF();
	if (far != NULL) *far = _far->getValueF();
}

void Camera::drawMark(RendererPtr render)
{
	if (_mark->getValueB()) {
		render->setModelTransform(_translations * _rotations);

		float size = _markSize->getValueF();
		int color = _markColor->getValueI();

		render->drawLine(Vec3(-size, 0.0f, 0.0f), Vec3(size, 0.0f, 0.0f), color);
		render->drawLine(Vec3(0.0f, -size, 0.0f), Vec3(0.0f, size, 0.0f), color);
		render->drawLine(Vec3(0.0f, 0.0f, -size), Vec3(0.0f, 0.0f, size), color);
	}
}

Mat4 Camera::ortho(float left, float right, float bottom, float top, float near, float far)
{
	return Mat4(
		Vec4(2.0f/(right-left), 0.0f, 0.0f, -(right+left)/(right-left)),
		Vec4(0.0f, 2.0f/(top-bottom), 0.0f, -(top+bottom)/(top-bottom)),
		Vec4(0.0f, 0.0f, -2.0f/(far-near), -(far+near)/(near-far)),
		Vec4(0.0f, 0.0f, 0.0f, 1.0f)
	);
}

Mat4 Camera::frustum(float left, float right, float bottom, float top, float near, float far)
{
	return Mat4(
		Vec4(-2.0f*near/(right-left), 0.0f, (right+left)/(right-left), 0.0f),
		Vec4(0.0f, -2.0f*near/(top-bottom), (top+bottom)/(top-bottom), 0.0f),
		Vec4(0.0f, 0.0f, (far+near)/(far-near), -2.0f*far*near/(far-near)),
		Vec4(0.0f, 0.0f, -1.0f, 0.0f)
	);
}

Mat4 Camera::perspective(float fovy, float aspect, float near, float far)
{
	float top = (float) (near*tan(fovy*M_PI/ 360.0f)) / _zoom->getValueF();
	float bottom = -top;
	float right = top * aspect;
	float left = -top * aspect;	

	return frustum(left, right, bottom, top, near, far);
}